/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.inspur.edp.bff.designtime.generator.changeconvertor.methodgenerator;

import com.inspur.edp.bef.bizentity.GspBizEntityElement;
import com.inspur.edp.bff.designtime.generator.changeconvertor.FSChangeConvertorGenContext;
import com.inspur.edp.bff.designtime.generator.common.GenUtils;
import com.inspur.edp.caf.generator.baseInfo.ParameterInfo;
import com.inspur.edp.caf.generator.baseInfo.TypeInfo;
import com.inspur.edp.caf.generator.method.ClassMethodGenerator;
import com.inspur.edp.caf.generator.utils.ExpressionUtils;
import com.inspur.edp.cef.entity.changeset.ModifyChangeDetail;
import com.inspur.edp.cef.entity.i18n.MultiLanguageInfo;
import com.inspur.edp.cef.generator.DotNetToJavaStringHelper;
import com.inspur.edp.formserver.viewmodel.GspViewModelElement;
import lombok.var;
import org.eclipse.jdt.core.dom.*;

import java.util.ArrayList;
import java.util.Arrays;

public class ConvertModifyPropertiesFormBeGenerator extends ClassMethodGenerator {

    private static final String ParamBeCode = "beElementCode";
    private static final String ParamBeValue = "beValue";
    private static final String ParamVmValue = "vmValue";
    private static final String ParamVmChange = "vmChange";
    private FSChangeConvertorGenContext changeConvertorContext;

    public ConvertModifyPropertiesFormBeGenerator(
        FSChangeConvertorGenContext changeConvertorContext) {
        this.changeConvertorContext = changeConvertorContext;
    }

    private void addElementValue(GspViewModelElement vmElement, GspBizEntityElement beElement,
        String valueName, SwitchStatement result) {

        var caseStatement = getSwitchCase(beElement.getLabelID());

        result.statements().add(caseStatement);
        result.statements().add(addChange(ParamVmChange, vmElement.getLabelID(), valueName, false));
        result.statements().add(ast.newBreakStatement());
    }


    // case "beLabelId" + MultiLanguageInfo.MULTILANGUAGETOKEN:
    private void addMultilanguageElementValue(String caseElementLabelId,
        String targetElementLabelId, SwitchStatement result) {
        var caseStatement = ast.newSwitchCase();
        var caseExpression = ast.newInfixExpression();
        var labelIdLiteral = ast.newStringLiteral();
        labelIdLiteral.setLiteralValue(caseElementLabelId);
        caseExpression.setLeftOperand(labelIdLiteral);
        caseExpression.setOperator(InfixExpression.Operator.PLUS);
        caseExpression.setRightOperand(ast.newName("MultiLanguageInfo.MULTILANGUAGETOKEN"));
        caseStatement.setExpression(caseExpression);

        result.statements().add(caseStatement);
        result.statements().add(
            getMutiLanguageInfoMethod(ParamVmChange, targetElementLabelId, ParamBeValue));
        result.statements().add(ast.newBreakStatement());

        addImport(MultiLanguageInfo.class.getTypeName());
    }

    //this.setVmChangeMultiLanguageInfo(vmChange, "voLabelId", beValue);
    private ExpressionStatement getMutiLanguageInfoMethod(String voChangeDetailPara,
        String elementLabelId, String beValuePara) {
        SuperMethodInvocation methodInvocation = ast.newSuperMethodInvocation();
        methodInvocation.setName(ast.newSimpleName("setChangeDetailMultiLanguageInfo"));
        methodInvocation.arguments().add(ast.newSimpleName(voChangeDetailPara));
        StringLiteral stringLiteral = ast.newStringLiteral();
        stringLiteral.setLiteralValue(elementLabelId);
        methodInvocation.arguments().add(stringLiteral);
        methodInvocation.arguments().add(ast.newSimpleName(beValuePara));
        return ast.newExpressionStatement(methodInvocation);
    }

    private SwitchCase getSwitchCase(String value) {
        var caseValue = ast.newStringLiteral();
        caseValue.setLiteralValue(value);
        var caseStatement = ast.newSwitchCase();
        caseStatement.setExpression(caseValue);

        return caseStatement;
    }

    private ExpressionStatement addChange(String change, String key, String valueName,
        boolean isKeyPro) {

        var changeInvocation = ExpressionUtils.getMethodInvocation(ast,change, "getPropertyChanges");
        var invocation = ExpressionUtils.getMethodInvocation(ast,changeInvocation, "put");

        Expression keyArgument;
        if (isKeyPro) {
            keyArgument = ast.newSimpleName(key);
        } else {
            var literal = ast.newStringLiteral();
            literal.setLiteralValue(key);
            keyArgument = literal;
        }

        invocation.arguments().add(keyArgument);
        invocation.arguments().add(ast.newSimpleName(valueName));

        return ast.newExpressionStatement(invocation);
    }

    @Override
    protected Block buildMethodBody() {
        Statement vmValueStatement = getVMValueDeclaration();
        SwitchStatement result = getSwitchBlock();

        for (var gspCommonField : changeConvertorContext.getVmObject().getContainElements()) {

            GspViewModelElement item = (GspViewModelElement) gspCommonField;
            if (item == null || item.getIsVirtual()) {
                continue;
            }
            if (DotNetToJavaStringHelper.isNullOrEmpty(item.getMapping().getTargetObjId()))
            //TODO 异常封装
            {
                throw new RuntimeException(
                    "#GspBefError# 字段" + item.getCode() + " 的Mapping字段ID不允许为空#GspBefError#");
            }

            if (item.getMapping().getTargetMetadataId().equals(changeConvertorContext.getVmGeneratorContext()
                .getVm().getMapping().getTargetMetadataId()) == false) {
                continue;
            }
            Object tempVar = changeConvertorContext.getBeObject().findElement(item.getMapping().getTargetObjId());
            GspBizEntityElement entityElement = (GspBizEntityElement) (
                (tempVar instanceof GspBizEntityElement) ? tempVar : null);
            if (entityElement == null) {
                continue;
            }

            addElementMapBlock(item, entityElement, result);
        }
        var block = ast.newBlock();
        block.statements().add(vmValueStatement);
        block.statements().add(result);
        return block;
    }

    private Statement getVMValueDeclaration() {
        var frag = ast.newVariableDeclarationFragment();
        frag.setName(ast.newSimpleName(ParamVmValue));
        frag.setInitializer(ast.newNullLiteral());
        var statement = ast.newVariableDeclarationStatement(frag);
        statement.setType(ast.newSimpleType(ast.newSimpleName("Object")));
        return statement;
    }

    //
//
    private void addElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement, SwitchStatement result) {
        if (vmElement.getIsUdt()) {
            //TODO
            addBasicElementMapBlock(vmElement, beElement, result);
            return;
        }
        switch (vmElement.getObjectType()) {
            case None:
            case DynamicProp:
                addBasicElementMapBlock(vmElement, beElement, result);
                break;
            case Association:
                addAssElementMapBlock(vmElement, beElement, result);
                break;
            case Enum:
                addEnumElementMapBlock(vmElement, beElement, result);
                break;
            default:
                throw new RuntimeException(
                    "#GspBefError# 错误的类型" + vmElement.getObjectType().toString()
                        + " #GspBefError# ");
        }
    }

    private void addBasicElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
        SwitchStatement result) {
        addElementValue(vmElement, beElement, ParamBeValue, result);
        if (!vmElement.getIsUdt() && vmElement.getIsMultiLanguage() && vmElement
            .isEnableMultiLanguageInput()) {
            this.addMultilanguageElementValue(beElement.getLabelID(), vmElement.getLabelID(),
                result);
        }
    }

    private void addAssElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
        SwitchStatement result) {

        result.statements().add(getSwitchCase(vmElement.getLabelID()));
        result.statements().add(getIfNullStatement(beElement, vmElement));
        result.statements().add(getAddValueStatement(vmElement));
        result.statements().add(ast.newBreakStatement());
    }

    private Statement getIfNullStatement(GspBizEntityElement beElement, GspViewModelElement vmElement) {


        var ifStatement = ast.newIfStatement();
        var condition = ast.newInfixExpression();
        condition.setLeftOperand(ast.newSimpleName(ParamBeValue));
        condition.setRightOperand(ast.newNullLiteral());
        condition.setOperator(InfixExpression.Operator.EQUALS);
        ifStatement.setExpression(condition);

        var thenBlock = ast.newBlock();
        var thenInvocation = ast.newAssignment();
        thenInvocation.setLeftHandSide(ast.newSimpleName(ParamVmValue));
        thenInvocation.setRightHandSide(ast.newNullLiteral());
        thenBlock.statements().add(ast.newExpressionStatement(thenInvocation));

        ifStatement.setThenStatement(thenBlock);

        Block elseblock = getElseStatements(beElement, vmElement);
        ifStatement.setElseStatement(elseblock);

        return ifStatement;
    }

    private Block getElseStatements(GspBizEntityElement beElement, GspViewModelElement vmElement) {

        var elseBlock = ast.newBlock();

        addGetBEAssStatement(elseBlock, beElement);
        newVMAssStatement(elseBlock, vmElement);
        assPropMapBlock(vmElement, beElement, elseBlock);
        setVMValueStatement(elseBlock);
        return elseBlock;
    }

    private void addGetBEAssStatement(Block block, GspBizEntityElement beElement) {

        String assoTypeName = null;
        var assoType = GenUtils.GetBEAssoType(changeConvertorContext.getVmGeneratorContext(), beElement,assoTypeName);

        var beAssoType =ast.newSimpleType(ast.newQualifiedName(
            ast.newName(assoType.getTypePackageName()),
            ast.newSimpleName(assoType.getTypeName())
        ));
        var frag = ast.newVariableDeclarationFragment();
        frag.setName(ast.newSimpleName("beAss"));
        var castExpression = ast.newCastExpression();
        castExpression.setExpression(ast.newSimpleName(ParamBeValue));
        castExpression.setType(beAssoType);
        frag.setInitializer(castExpression);

        var statement = ast.newVariableDeclarationStatement(frag);

        var beAssoType1 =ast.newSimpleType(ast.newQualifiedName(
            ast.newName(assoType.getTypePackageName()),
            ast.newSimpleName(assoType.getTypeName())
        ));
        statement.setType(beAssoType1);

        block.statements().add(statement);
    }

    private void newVMAssStatement(Block elseBlock,
        GspViewModelElement vmElement) {

        var vmAssoTypeName = vmElement.getAssociationTypeName();

        var type = ast.newSimpleType(ast.newSimpleName(vmAssoTypeName));
        var frag = ast.newVariableDeclarationFragment();
        frag.setName(ast.newSimpleName("vmAss"));
        var creation = ast.newClassInstanceCreation();
        creation.setType(type);
        frag.setInitializer(creation);
        var statement = ast.newVariableDeclarationStatement(frag);
        var type1 = ast.newSimpleType(ast.newSimpleName(vmAssoTypeName));
        statement.setType(type1);

        elseBlock.statements().add(statement);
    }

    private void setVMValueStatement(Block elseBlock) {
        var assignment = ast.newAssignment();
        assignment.setLeftHandSide(ast.newSimpleName(ParamVmValue));
        assignment.setRightHandSide(ast.newSimpleName("vmAss"));
        elseBlock.statements().add(ast.newExpressionStatement(assignment));
    }


    private void assPropMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement, Block elseBlock) {

        for (var gspCommonField : vmElement.getChildAssociations().get(0).getRefElementCollection()) {

            var item = (GspViewModelElement) gspCommonField;
            if (item == null) {
                continue;
            }

            GspBizEntityElement beAssElement = (GspBizEntityElement) (
                (beElement.getChildAssociations().get(0).getRefElementCollection()
                    .getItem(item.getMapping().getTargetObjId()) instanceof GspBizEntityElement)
                    ? beElement.getChildAssociations().get(0).getRefElementCollection()
                    .getItem(item.getMapping().getTargetObjId())
                    : null);
            if (beAssElement == null) {
                throw new RuntimeException(
                    "找不到Vo[" + item.getBelongObject().getBelongModel().getCode() + "]中字段["
                        + item.getLabelID() + "]对应的be字段");
            }
            var beData = ExpressionUtils.getMethodInvocation(ast,"beAss", "get" + beAssElement.getLabelID());
            var voData = ExpressionUtils.getMethodInvocation(ast,"vmAss", "set" + item.getLabelID());
            voData.arguments().add(beData);
            elseBlock.statements().add(ast.newExpressionStatement(voData));
        }
        var beData = ExpressionUtils
            .getMethodInvocation(ast, "beAss", "get" + beElement.getLabelID());
        var voData = ExpressionUtils
            .getMethodInvocation(ast, "vmAss", "set" + vmElement.getLabelID());
        voData.arguments().add(beData);

        elseBlock.statements().add(ast.newExpressionStatement(voData));
    }


    private Statement getAddValueStatement(GspViewModelElement vmElement) {

        var changeInvocation = ExpressionUtils.getMethodInvocation(ast,ParamVmChange, "getPropertyChanges");
        var invocation = ExpressionUtils.getMethodInvocation(ast,changeInvocation, "put");

        var literal = ast.newStringLiteral();
        literal.setLiteralValue(vmElement.getLabelID());
        invocation.arguments().add(literal);
        invocation.arguments().add(ast.newSimpleName(ParamVmValue));

        return ast.newExpressionStatement(invocation);
    }

    private void addEnumElementMapBlock(GspViewModelElement vmElement, GspBizEntityElement beElement,
        SwitchStatement result) {

        String enumClassName = null;
        TypeInfo enumType = GenUtils
            .getBeEnumType(changeConvertorContext.getVmGeneratorContext(), beElement,
                enumClassName);

        var varName = "beEnumValue" + vmElement.getLabelID();

        result.statements().add(getSwitchCase(beElement.getLabelID()));
        result.statements().add(getEnumValueStatement(enumType, varName));
        result.statements().add(addEnumChange(beElement, varName, vmElement));
        result.statements().add(ast.newBreakStatement());

    }
    private Statement getEnumValueStatement(TypeInfo beEnumType, String varName) {
        var enumType = ast.newSimpleType(ast.newQualifiedName(
            ast.newName(beEnumType.getTypePackageName()),
            ast.newSimpleName(beEnumType.getTypeName())
        ));

        var frag = ast.newVariableDeclarationFragment();
        frag.setName(ast.newSimpleName(varName));

        var castExpression = ast.newCastExpression();
        castExpression.setType(enumType);
        castExpression.setExpression(ast.newSimpleName(ParamBeValue));
        frag.setInitializer(castExpression);
        var enumType1 = ast.newSimpleType(ast.newQualifiedName(
            ast.newName(beEnumType.getTypePackageName()),
            ast.newSimpleName(beEnumType.getTypeName())
        ));
        var statement = ast.newVariableDeclarationStatement(frag);
        statement.setType(enumType1);
        return statement;
    }

    private IfStatement addEnumChange(GspBizEntityElement beElement, String varName, GspViewModelElement vmElement) {
        IfStatement ifStatement = ast.newIfStatement();
        InfixExpression infixExpression =  ast.newInfixExpression();
        infixExpression.setLeftOperand(ast.newSimpleName(varName));
        infixExpression.setOperator(InfixExpression.Operator.EQUALS);
        infixExpression.setRightOperand(ast.newNullLiteral());
        ifStatement.setExpression(infixExpression);

        var enumType = vmElement.getEnumTypeName();

        var changeInvocation1 = ExpressionUtils.getMethodInvocation(ast,ParamVmChange, "getPropertyChanges");
        Block thenBlock = ast.newBlock();
        MethodInvocation thenMtheod = ast.newMethodInvocation();
        thenMtheod.setExpression(changeInvocation1);
        thenMtheod.setName(ast.newSimpleName("put"));
        StringLiteral stringLiteral = ast.newStringLiteral();
        stringLiteral.setLiteralValue(beElement.getLabelID());
        thenMtheod.arguments().add(stringLiteral);
        thenMtheod.arguments().add(ast.newNullLiteral());
        thenBlock.statements().add(ast.newExpressionStatement(thenMtheod));
        ifStatement.setThenStatement(thenBlock);

        var changeInvocation = ExpressionUtils.getMethodInvocation(ast,ParamVmChange, "getPropertyChanges");
        var invocation = ExpressionUtils.getMethodInvocation(ast,changeInvocation, "put");

        var literal = ast.newStringLiteral();
        literal.setLiteralValue(vmElement.getLabelID());
        invocation.arguments().add(literal);

        var arg = ast.newArrayAccess();
        var array = ast.newMethodInvocation();
        array.setExpression(ast.newSimpleName(enumType));
        array.setName(ast.newSimpleName("values"));
        arg.setArray(array);

        var index = ExpressionUtils.getMethodInvocation(ast, varName, "ordinal");
        arg.setIndex(index);
        invocation.arguments().add(arg);

        Block elseBlock = ast.newBlock();
        elseBlock.statements().add(ast.newExpressionStatement(invocation));
        ifStatement.setElseStatement(elseBlock);

        return ifStatement;
    }

    private SwitchStatement getSwitchBlock() {
        SwitchStatement statement = ast.newSwitchStatement();
        statement.setExpression(ast.newSimpleName(ParamBeCode));
        return statement;
    }

    @Override
    protected ArrayList<Modifier.ModifierKeyword> getAccessModifiers() {
        return new ArrayList<Modifier.ModifierKeyword>(Arrays.asList(
            new Modifier.ModifierKeyword[]{Modifier.ModifierKeyword.PROTECTED_KEYWORD}));
    }

    @Override
    protected boolean getIsOverride() {
        return true;
    }

    @Override
    protected String getMethodName() {
        return "convertModifyPropFromBE";
    }

    @Override
    protected ArrayList<ParameterInfo> getParameterCollection() {
        ArrayList<ParameterInfo> list = new ArrayList<ParameterInfo>();
        ParameterInfo paramBeCode = new ParameterInfo();
        paramBeCode.setParamType(new TypeInfo(String.class));
        paramBeCode.setParamName(ParamBeCode);
        list.add(paramBeCode);
        ParameterInfo paramBeValue = new ParameterInfo();
        paramBeValue.setParamType(new TypeInfo(Object.class));
        paramBeValue.setParamName(ParamBeValue);
        list.add(paramBeValue);
        ParameterInfo paramChange = new ParameterInfo();
        paramChange.setParamType(new TypeInfo(ModifyChangeDetail.class));
        paramChange.setParamName(ParamVmChange);
        list.add(paramChange);
        return list;
    }

    @Override
    protected TypeInfo getReturnType() {
        return new TypeInfo("void");
    }
}
